/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.gobblin.cli;

import java.util.ArrayList;
import java.util.List;

import com.google.common.base.Preconditions;
import com.google.common.primitives.Ints;


/**
 * A format helper for CLI output. Unfortunately it only supports strings, so
 * values need to be converted previous to passing in. This is done in order to
 * support table-like formatting.
 * <p/>
 * It's recommended that this class is built using the inner {@link Builder} class.
 *
 * @author ahollenbach@nerdwallet.com
 */
public class CliTablePrinter {
  /**
   * Labels for each columns
   */
  private List<String> labels;

  /**
   * A list of sprintf-style flag strings (corresponding to each column)
   */
  private List<String> flags;

  /**
   * Overall indentation of a table
   */
  private int indentation;

  /**
   * Number of spaces to place between columns
   */
  private int delimiterWidth;

  /**
   * Table of data to print
   */
  private List<List<String>> data;

  /**
   * The row format (generated by the constructor).
   */
  private String rowFormat;


  public CliTablePrinter(List<String> labels, List<String> flags, int indentation, int delimiterWidth,
      List<List<String>> data) {
    Preconditions.checkArgument(data.size() > 0);
    Preconditions.checkArgument(data.get(0).size() > 0);

    if (labels != null) {
      Preconditions.checkArgument(data.get(0).size() == labels.size());
    }
    if (flags != null) {
      Preconditions.checkArgument(data.get(0).size() == flags.size());
    }

    this.labels = labels;
    this.flags = flags;
    this.indentation = indentation;
    this.delimiterWidth = delimiterWidth;
    this.data = data;

    this.rowFormat = getRowFormat(getColumnMaxWidths());
  }

  /**
   * Used to build a {@link CliTablePrinter} object.
   */
  public static final class Builder {
    private List<String> labels;
    private List<String> flags;
    private int indentation;
    private int delimiterWidth;
    private List<List<String>> data;

    public Builder() {
      // Set defaults
      this.delimiterWidth = 1;
    }

    public Builder labels(List<String> labels) {
      this.labels = labels;
      return this;
    }

    public Builder data(List<List<String>> data) {
      this.data = data;
      return this;
    }

    public Builder indentation(int indentation) {
      this.indentation = indentation;
      return this;
    }

    public Builder delimiterWidth(int delimiterWidth) {
      this.delimiterWidth = delimiterWidth;
      return this;
    }

    public Builder flags(List<String> flags) {
      this.flags = flags;
      return this;
    }

    public CliTablePrinter build() {
      return new CliTablePrinter(this.labels, this.flags, this.indentation, this.delimiterWidth, this.data);
    }
  }

  /**
   * Prints the table of data
   */
  public void printTable() {
    if (this.labels != null) {
      System.out.printf(this.rowFormat, this.labels.toArray());
    }
    for (List<String> row : this.data) {
      System.out.printf(this.rowFormat, row.toArray());
    }
  }

  /**
   * A function for determining the max widths of columns, accounting for labels and data.
   *
   * @return An array of maximum widths for the strings in each column
   */
  private List<Integer> getColumnMaxWidths() {
    int numCols = data.get(0).size();
    int[] widths = new int[numCols];

    if (this.labels != null) {
      for (int i=0; i<numCols; i++) {
        widths[i] = this.labels.get(i).length();
      }
    }

    for (List<String> row : this.data) {
      for (int i=0;i<row.size(); i++) {
        if (row.get(i) == null) {
          widths[i] = Math.max(widths[i], 4);
        } else {
          widths[i] = Math.max(widths[i], row.get(i).length());
        }
      }
    }

    return Ints.asList(widths);
  }

  /**
   * Generates a simple row format string given a set of widths
   *
   * @param widths A list of widths for each column in the table
   * @return A row format for each row in the table
   */
  private String getRowFormat(List<Integer> widths) {
    StringBuilder rowFormat = new StringBuilder(spaces(this.indentation));
    for (int i=0; i< widths.size(); i++) {
      rowFormat.append("%");
      rowFormat.append(this.flags != null ? this.flags.get(i) : "");
      rowFormat.append(widths.get(i).toString());
      rowFormat.append("s");
      rowFormat.append(spaces(this.delimiterWidth));
    }
    rowFormat.append("\n");

    return rowFormat.toString();
  }

  private static String spaces(int numSpaces) {
    StringBuilder sb = new StringBuilder();
    for (int i=0; i<numSpaces; i++) {
      sb.append(" ");
    }
    return sb.toString();
  }
}
